# coding=utf-8
# require `pip3 install pyserial`
# or     `pip2 install serial`
import sys
import serial
import serial.tools.list_ports
import threading
import time
import os
# 用于统计时间
import datetime
import argparse, socket
import logging

# 填入你需要的端口(如果失败了，会尝试另外的COM)
COM_PORT='COM4'
COM_BITR=115200

log_to_file= False
log_to_file_dir = './log/'

global ser
global cmdUpdateRequestStr
global xmodeStr
global fineName

cmdUpdateRequestStr = '01 88 00 05'
fineName = "./Project.bin"


# xmode  协议
SOH = b'\x01'
STX = b'\x02'
EOT = b'\x04'
ACK = b'\x06'
NAK = b'\x15'
CAN = b'\x18'
CTRLZ = b'\x1A'




# ######################################## LOG # ########################################
# logging
formatter="%(asctime)s %(levelname)-12s %(message)s"

if log_to_file :
    try :
        os.mkdir(log_to_file_dir)
    except :
        pass
    log_filename=log_to_file_dir + "/log_"+datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d.%H.%M.%S.%f')+".txt"
    logging.basicConfig(filename=log_filename, filemode="a", format=formatter, level=logging.INFO);
logging.basicConfig(filemode="w", format=formatter, level=logging.INFO);

# to console
formatter = logging.Formatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
if log_to_file:
    logging.getLogger('').addHandler(console)

# ######################################## CRC # ########################################
table_crc_hi = (
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40)
table_crc_lo = (
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
    0x43, 0x83, 0x41, 0x81, 0x80, 0x40)

crc16tab = (
    0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
    0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
    0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
    0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
    0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
    0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
    0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
    0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
    0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
    0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
    0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
    0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
    0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
    0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
    0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
    0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
    0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
    0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
    0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
    0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
    0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
    0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
    0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
    0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
    0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
    0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
    0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
    0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
    0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
    0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
    0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
    0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
)

def crc16_ccitt(st):
    global crc16tab
    L = []
    st = st.strip().replace(' ', '')
    if len(st) % 2:
        return None
    for j in range(0, len(st), 2):
        L.append(int(st[j:(j + 2)], 16))
    connect = 0
    crc = 0
    crc_hi = crc_lo = 0
    for content in L:
        index = crc_hi ^ content
        temp = crc16tab[index]
        crc = crc_lo ^ temp;
        #20200525yu: 下面这一步很关键，python的整形类型的长度是很长的，所以要与一下
        crc &= 0xffff
        crc_hi = crc>>8
        crc_lo = crc<<8
    result = []
    result.append(crc_hi)
    result.append(crc&0x00ff)
    return bytes(result)


# ######################################## LOOP TEST # ########################################

global testUnitCnt
global testCommand

def setTestTime( cnt) :
    global testUnitCnt
    testUnitCnt = cnt

# ######################################## Data Exchange # ########################################
def int32ToHexStr(value) :
    # 整数转换 高低字节
    vH = b''
    vL = b''
    high = value  & 65280
    high = high >> 8
    low  = value & 255

    vH = str(hex(high))[2:]
    vL = str(hex(low ))[2:]

    vH = vH.rjust(2,'0')
    vL = vL.rjust(2,'0')

    ret = vH + ' '+ vL + ' '
    return ret

def int16ToHexStr(value) :
    vL = b''
    low  = value & 255
    #logging.info ('value = %d'%(value))
    #logging.info ('low   = %d'%(low))

    vL = str(hex(low ))[2:]

    vL = vL.rjust(2,'0')

    ret = vL + ' '
    return  ret


def setTestTime( cnt) :
    global testUnitCnt
    testUnitCnt = cnt

# ######################################## SERIAL # ########################################

def initSerial () :
    ser = None
    try:
        ser = serial.Serial(COM_PORT, COM_BITR, timeout=5)
        return ser
    except Exception as e:
        plist = list(serial.tools.list_ports.comports())

        if len(plist) <= 0:
            print("The Serial port can't find!")
        else:
            plist_0 = list(plist[0])
            serialName = plist_0[0]
            ser = serial.Serial(serialName, COM_BITR, timeout=5)
            print("check which port was really used >", ser.name)
    return ser

def deinitSerial(ser) :
    ser.close()

# ######################################## MCU-UI # ########################################

def waitCmdAck (ser, cmdStr ) :
    out = ''
    recv= ''
    tmp = cmdStr.replace(' ', '', -1)
    ackInt = int(tmp[6:8], 16)
    ack = str(hex(ackInt))[2:]
    start = time.time()
    i = 0
    while True :
        try:
            if i == 10:
                return
            i = i + 1
            num = ser.inWaiting()
            if(num <= 0):
                time.sleep(0.1)
                continue
            try :
                out = ser.read(1).hex()  # 一个一个的读取
            except Exception as e :
                logging.info('failed when waitCmdAck!')
                sys.exit()
                pass

            if out != '' :
                recv = out
                out = ''
            if recv == ack :
                end = time.time()
                break
        except Exception as e:
            return None

    logging.info ('recv %s'%(recv))
    logging.info (end - start)
    return (end - start)

def sendCmdStr(ser, cmdStr) :
    cmd = bytes.fromhex(cmdStr)
    try :
        ser.write(cmd)
    except Exception as e :
        logging.info('failed when sendCmdStr!')
        print("Exception: %s" % str(e))
        sys.exit()
        pass

def requestUpdate(ser) :
    #logging.info('TODO requestUpdate')
    sendCmdStr(ser, cmdUpdateRequestStr)
    waitCmdAck(ser, cmdUpdateRequestStr)
    return

def waitForMCUReboot() :
    logging.info("waitForMCUReboot")
    time.sleep(1)
    pass

def cmdUpdateRequestTestUint(cnt) :
    global testUnitCnt
    global fineName
    setTestTime(cnt);
    logging.info('cmdUpdateRequestTestUint :  [%d] time(s)'%(testUnitCnt))
    logging.info("FilName is %s"%(fineName))
    while True :
        if testUnitCnt == 0 :
            break
        testUnitCnt = testUnitCnt - 1

        ser = initSerial()
        requestUpdate(ser)
        waitForMCUReboot()
        checkForC (ser)
        sendFile(ser, fineName)
        deinitSerial(ser)
        logging.info('[%d]Test done, wait for Next Text'%(testUnitCnt))
        time.sleep(2) # 等待MCU重启

def checkForC (ser) :
    # 等到下位机主动发C,否则不退出
    t_count = 0
    logging.info("Checking for \'C\'")
    ret = False
    while True:
        num = ser.inWaiting()
        t_count += 1
        if t_count >= 65535:
            break
        if num > 0:
            data = ser.read(num)
            num = len(data)

            try: # 尝试 string 显示，不行就hex显示
                print(str(data, 'UTF-8'))
            except Exception as e:
                out_s = ''
                for i in range(0, len(data)):
                    out_s = out_s + '{:02X}'.format(data[i]) + ' '
                print(out_s)
            if (data[-1] == b'C'[0] or data[-1] == b'\x15'[0]):
               return True
    return False

def sendFile (ser, fineName) :
    filesize = os.path.getsize(fineName)
    logging.info("File name :" + fineName)
    logging.info("File size :" + str(filesize))
    file = open(fineName, 'rb')
    size_count = 0
    package_count = 1
    start = time.time()
    while True:
        logging.info(' %d / %d'%(size_count, filesize))
        if size_count >= filesize :
            ser.write(EOT)
            rec = ser.read(1)
            if rec == ACK:
                logging.info('Update Success!')
                break
            else :
                logging.info('Update failed as NACK!')
                sys.exit()
                break
        content = file.read(1024)
        read_length = len(content)
        #20200518yu: 不足1024的时候补0x1A
        if read_length < 1024:
            addition = b''
            for i in range(1024 - read_length):
                addition += CTRLZ
            content += addition
        data = b''
        data += STX
        list_pc = []
        list_pc.append(package_count)
        list_pc.append(255-package_count)
        data += bytes(list_pc)
        data += content
        data += crc16_ccitt(content.hex())

        #发送数据,等待接收完成.返回错误码就重发
        while True:
            try:
                #logging.info('Send size is ' + str(len(data)))
                ser.write(data)
                rec = None
                t_count = 0
                #logging.info('Wait ACK ')
                rec = ser.read(1)
            except Exception as e:
                logging.info('failed when crc!')
                print("Exception: %s" % str(e))
                sys.exit()
            if rec == ACK:
                #logging.info('GO ACK!')
                break  #继续
            elif rec == NAK:
                logging.info('GO NACK!')
            continue
        size_count += read_length
        package_count += 1
        if package_count >= 255:
            package_count = 1

    end = time.time()
    logging.info ("Time cost : " + str(end - start))
    file.close()
    return (end - start)

if __name__ == '__main__':
    logging.info('CON TEST with Subject : UART 0 Update v2023.03.23')
    cmdUpdateRequestTestUint(1)

